package com.musollo.mp3tools.jaudiotagger;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.log4j.Logger;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.KeyNotFoundException;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.TagField;
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
import org.jaudiotagger.tag.id3.ID3v24Frames;

import com.musollo.domain.MP3;
import com.musollo.exceptions.ParseException;
import com.musollo.mp3tools.TagReader;

/**
 * This class implements the ITagReader interface and uses the JAudioTagger
 * library to read MP3 tags.
 * 
 * @see TagReader
 * @author paw.janssen
 * 
 */
public class JAudioTaggerReader implements TagReader {

	private static final Logger LOGGER = Logger.getLogger(JAudioTaggerReader.class);

	/**
	 * This static method can be used to retreive a JAudioTagger audioFile, from
	 * which JAudioTagger tags can be directly retreived.
	 * 
	 * @param mp3File
	 *            - The actual mp3 harddisk file to parse.
	 * @return JAudioTagger MP3File - The JAudioTagger MP3File instance.
	 * @throws ParseException
	 *             - Thrown if the file could not be parsed.
	 */
	public static MP3File parseFile(final File mp3File) throws ParseException {
		LOGGER.info("Parsing file: " + mp3File);

		MP3File audioTaggerFile;
		try {
			audioTaggerFile = (MP3File) AudioFileIO.read(mp3File);
		} catch (CannotReadException e) {
			throw new ParseException("Cannot read file", e);
		} catch (IOException e) {
			throw new ParseException("Cannot read file", e);
		} catch (TagException e) {
			throw new ParseException("Cannot read tag", e);
		} catch (ReadOnlyFileException e) {
			throw new ParseException("File is read only", e);
		} catch (InvalidAudioFrameException e) {
			throw new ParseException("Invalid audio frame", e);
		}

		return audioTaggerFile;
	}

	private static int parseNumber(final String number) {
		try {
			return Integer.parseInt(number);
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	private File mp3File;

	private MP3File audioTaggerFile;

	private MP3 mp3;

	private String getV1TagFieldValue(final Tag t, final FieldKey key) {
		try {
			return t.getFirst(key);
		} catch (KeyNotFoundException e) {
			LOGGER.warn("Key not found " + key, e);
		} catch (Exception e) {
			LOGGER.error("Unexcepted exception " + key, e);
		}

		return "0";
	}

	private String getV24TagFieldValue(final AbstractID3v2Tag t, final String key) {
		try {
			return t.getFirst(key);
		} catch (KeyNotFoundException e) {
			LOGGER.warn("Key not found " + key, e);
		} catch (Exception e) {
			LOGGER.error("Unexcepted excetpion " + key, e);
		}

		return "0";
	}

	private void parseTag() {
		if (this.audioTaggerFile.hasID3v2Tag()) {
			LOGGER.debug("File has ID3v2Tag");
			this.parseV24Tag();
		} else if (this.audioTaggerFile.hasID3v1Tag()) {
			LOGGER.debug("File has ID3v1Tag");
			this.parseV1Tag();
		} else {
			LOGGER.debug("File has no tag");
			this.mp3 = new MP3(this.mp3File, 0);
		}
	}

	private void parseV1Tag() {
		LOGGER.debug("Parsing ID3v1Tag");

		Tag tag = this.audioTaggerFile.getID3v1Tag();
		AudioHeader header = this.audioTaggerFile.getAudioHeader();

		// instantiate the mp3 class
		this.mp3 = new MP3(this.mp3File, header.getTrackLength());

		this.mp3.setArtist(getV1TagFieldValue(tag, FieldKey.ARTIST));

		this.mp3.setAlbum(getV1TagFieldValue(tag, FieldKey.ALBUM));

		this.mp3.setAlbumTrackNumber(parseNumber(getV1TagFieldValue(tag, FieldKey.TRACK)));

		this.mp3.setGenre(getV1TagFieldValue(tag, FieldKey.GENRE));

		this.mp3.setTitle(getV1TagFieldValue(tag, FieldKey.TITLE));

		this.mp3.setDate(getV1TagFieldValue(tag, FieldKey.YEAR));

		List<TagField> comments = tag.getFields(FieldKey.COMMENT);
		String mp3Comment = new String();
		for (TagField comment : comments) {
			mp3Comment += comment.toString();
		}

		this.mp3.setComment(mp3Comment);
	}

	private void parseV24Tag() {
		LOGGER.debug("Parsing ID3v2Tag");

		AbstractID3v2Tag tag = this.audioTaggerFile.getID3v2TagAsv24();
		AudioHeader header = this.audioTaggerFile.getAudioHeader();

		// instantiate the mp3 class
		this.mp3 = new MP3(this.mp3File, header.getTrackLength());

		this.mp3.setArtist(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_ARTIST));

		this.mp3.setAlbum(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_ALBUM));

		this.mp3.setAlbumTrackNumber(parseNumber(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_TRACK)));

		this.mp3.setGenre(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_GENRE));

		this.mp3.setTitle(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_TITLE));

		this.mp3.setDate(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_YEAR));

		this.mp3.setComment(getV24TagFieldValue(tag, ID3v24Frames.FRAME_ID_COMMENT));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public MP3 readMP3(final File mp3File) throws FileNotFoundException, ParseException {
		// create a new mp3 class file
		if (mp3File.exists()) {
			this.mp3File = mp3File;

			LOGGER.debug("File " + this.mp3File + " exists");

			this.audioTaggerFile = JAudioTaggerReader.parseFile(mp3File);
			this.parseTag();

			return this.mp3;
		} else {
			throw new FileNotFoundException("File " + this.mp3File + " was not found on the system");
		}
	}

}
